Interactive Mapping using the Leaflet package in R

R Markdown

This is an R Markdown document. Markdown is a simple formatting syntax for authoring HTML, PDF, and MS Word documents. For more details on using R Markdown see http://rmarkdown.rstudio.com.

Insert Chunk

Chunk is for running the code

# ctrl+alt+I

Load package

# CRAN version
if (!require('leaflet')) install.packages('leaflet')

# Or Github version
if (!require('devtools')) install.packages('devtools')
devtools::install_github('rstudio/leaflet')
## glue (1.7.0 -> 1.8.0) [CRAN]
## 
##   There is a binary version available but the source version is later:
##      binary source needs_compilation
## glue  1.7.0  1.8.0              TRUE
## 
##   Binaries will be installed
## package 'glue' successfully unpacked and MD5 sums checked
## 
## The downloaded binary packages are in
##  C:\Users\yanawu\AppData\Local\Temp\Rtmp4YNTLc\downloaded_packages
## ── R CMD build ─────────────────────────────────────────────────────────────────
##          checking for file 'C:\Users\yanawu\AppData\Local\Temp\Rtmp4YNTLc\remotes1081847e76145\rstudio-leaflet-9bf137b/DESCRIPTION' ...     checking for file 'C:\Users\yanawu\AppData\Local\Temp\Rtmp4YNTLc\remotes1081847e76145\rstudio-leaflet-9bf137b/DESCRIPTION' ...   ✔  checking for file 'C:\Users\yanawu\AppData\Local\Temp\Rtmp4YNTLc\remotes1081847e76145\rstudio-leaflet-9bf137b/DESCRIPTION' (441ms)
##       ─  preparing 'leaflet': (1.8s)
##    checking DESCRIPTION meta-information ...     checking DESCRIPTION meta-information ...   ✔  checking DESCRIPTION meta-information
##       ─  checking for LF line-endings in source and make files and shell scripts (904ms)
##       ─  checking for empty or unneeded directories
##   Removed empty directory      Removed empty directory 'leaflet/vignettes'
##       ─  building 'leaflet_2.2.2.9000.tar.gz'
##      
## 
# Or manually load packages in R
# Packages - Install

Set up work directory

library("rstudioapi") 

script_path <- normalizePath(dirname(rstudioapi::getActiveDocumentContext()$path))
setwd(script_path)

Load leaflet package

Basemap

The function leaflet() is called, followed by different layers with add*(). The pipe operator %>% is used to add layers on top of each other.

Many free third-party basemaps can be added using the addProviderTiles()

See here for the complete list of the third-oarty basemaps

basemap <- leaflet() %>%
  addProviderTiles(
    "CartoDB.Positron",
    group = "CartoDB"
  )  %>%
  addProviderTiles(
    "OpenStreetMap",
    # give the layer a name
    group = "OpenStreetMap"
  ) %>% 
  addProviderTiles("OpenTopoMap",group = 'Topology')

basemap

Exercise 01: Set initial view for basemap

Using setView() to set up the view of map (center and zoom level)

Using the center of Worcester: longitude:-71.81, latitude; 42.27, zoom = 15

zoom_basemap = basemap %>% setView(lng = -71.81, lat = 42.27, zoom = 15)
zoom_basemap

Exercise 02: Add UI Control

Using addLayersControl to switch layers on and off.

Two important parameters: baseGroups and position

You need to assign a name to each layer using groups and then use the function to define the base groups (layers that can be selected one at a time).

zoom_basemap %>% addLayersControl(baseGroups = c('Topology', "OpenStreetMap","CartoDB"),
                                  position = 'topleft')

Point data

Add single marker or AwesomeIcon

You can place a marker on the plot by providing coordinates through the lng (longitude) and lat (latitude) arguments of the addMarkers function. This function accepts either a single value or a vector of values for each argument.

basemap %>% 
  addMarkers(lat = 42.252,
             lng =  -71.824,
             label = "Clark")
icon.home <- makeAwesomeIcon(
  icon = "home", markerColor = "blue",
  library = "fa",
  iconColor = "white"
)

basemap %>%
  addAwesomeMarkers(
    lat = 42.252,
    lng =  -71.824,
    label = "Clark",
    icon = icon.home
  )

Add multiple locations

Worcester, MA, is home to numerous parks that offer a variety of amenities, enriching the quality of life for its residents. The data below represents a selection of parks in the city and serves as a demonstration for educational purposes. It does not encompass all parks in Worcester.

parks.csv data includes the location for several parks in Worcester city

parks = read.csv('parks.csv')
head(parks)
##         park_name        x       y
## 1        Elm Park -71.8165 42.2674
## 2 Green Hill Park -71.7883 42.2813
## 3  Institute Park -71.8085 42.2743
## 4       East Park -71.7874 42.2635
## 5     Newton Hill -71.8342 42.2712
## 6   Crompton Park -71.8050 42.2482

Exercise 03: Using addMarker to add Parks (multiple) to the basemap

lng and lat can be a vector, get the x and y from parks

A vector in R is the most basic data structure and is used to store a collection of values of the same type. Vectors can hold numeric, character, logical, or other types of data, but each vector must contain only one data type.

basemap %>% addMarkers(lng = parks$x, lat = parks$y,label = parks$park_name)

Add multiple locations: customize icon

icons_list <- icons(iconUrl = 'https://raw.githubusercontent.com/gisynw/ssj-30262/refs/heads/main/docs/Lectures/Week06_R_Mapping/trees.png', iconWidth = 20, iconHeight = 20)


basemap %>% addMarkers(lng = parks$x, lat = parks$y, icon = icons_list, label = parks$park_name)

Exercise 04: Add popup or label information

Add value from from names column in Parks using popup parameter or label parameter to see the difference

basemap %>% addMarkers(lng = parks$x, lat = parks$y, icon = icons_list, popup = parks$park_name )

Add Point Shapefile

Data Source: MassGIS Data: MassDEP Water Quality Monitoring Stations

Step 1: Check the projection of the leaflet Map

print(c('projection of basemap: ',  basemap$x$options$crs$crsClass))
## [1] "projection of basemap: " "L.CRS.EPSG3857"

Step 2: Do the Transformation

library(sf )
## Linking to GEOS 3.12.1, GDAL 3.8.4, PROJ 9.3.1; sf_use_s2() is TRUE
water_quality = read_sf('./water_quality_station/worcester_station.shp')
print(c("original coordinate system:" , st_crs(water_quality)$epsg))
## [1] "original coordinate system:" "26986"
tf_water <- st_transform(water_quality, crs = '+proj=longlat +datum=WGS84')
print(tf_water$geometry)
## Geometry set for 66 features 
## Geometry type: POINT
## Dimension:     XY
## Bounding box:  xmin: -71.85575 ymin: 42.22497 xmax: -71.74551 ymax: 42.30798
## Geodetic CRS:  +proj=longlat +datum=WGS84
## First 5 geometries:
## POINT (-71.83102 42.25056)
## POINT (-71.82584 42.23909)
## POINT (-71.85575 42.23359)
## POINT (-71.78715 42.22497)
## POINT (-71.80314 42.24174)

Step 3: Define a color palette based on the ‘category’ column

qualitative palette

unique(tf_water$SURVEYTYPE)
## [1] "Benthic Macroinvertebrate" "Fish Toxics"              
## [3] "Water Quality"             "Fish Population"
# devtools::install_github("awhstin/awtools")
library('awtools')

palette_pts <- colorFactor(palette = mpalette, domain = tf_water$SURVEYTYPE)
leaflet(data = tf_water) %>%
  addTiles() %>%  # Add a basemap
  addCircleMarkers(
    fillColor  = ~palette_pts(SURVEYTYPE),  # Color based on the 'category' column
    fillOpacity = 0.8,           # Adjust opacity
    radius = 5,                  # Set marker size
    stroke = TRUE,               # Add stroke to the markers
    weight = 1,                   # Set stroke weight
  color = "#000000",
  label = tf_water$SURVEYTYPE
    ) 

Exercise 05: Add Legend

leaflet(data = tf_water) %>%
  addTiles() %>%  # Add a basemap
  addCircleMarkers(
    fillColor  = ~palette_pts(SURVEYTYPE),  # Color based on the 'category' column
    fillOpacity = 0.8,           # Adjust opacity
    radius = 5,                  # Set marker size
    stroke = TRUE,               # Add stroke to the markers
    weight = 1,                   # Set stroke weight
  color = "#000000",
  label = tf_water$SURVEYTYPE
    ) %>% 
addLegend(data = tf_water,
          position = "bottomright",
          pal = palette_pts, 
          values = ~SURVEYTYPE,
          title = "Survey Type",
          opacity = 1)

Add Polygon

Before adding Polygon, Please transform your map to the leaflet projection

# importing rstudioapi package 
library("rstudioapi")  
  
# retrieving path from getSourceEditorContext()  
# using $ operator  
getSourceEditorContext()$path  
## [1] "M:/Github_repo/ssj-30262/docs/Lectures/Week06_R_Mapping/Week06_R_Mapping.Rmd"
getwd()
## [1] "M:/Github_repo/ssj-30262/docs/Lectures/Week06_R_Mapping"
water_pond = read_sf('.\\Lakes_Ponds_Rivers\\Lakes_Ponds_Rivers.shp')
tf_pond <- st_transform(water_pond, crs = '+proj=longlat +datum=WGS84')
leaflet() %>%
   addProviderTiles(
    "CartoDB.Positron",
    group = "OpenTopo"
  ) %>%
  addPolygons(data = tf_pond,  color = "blue", label = tf_pond$NAME, fillOpacity = 1)

Add Lines

Data Source: Winter Parking Ban

Step 1: Load data

parking_line = read_sf('.\\winter_ban\\Winter_Parking_Ban.shp')
tf_line <- st_transform(parking_line, crs = '+proj=longlat +datum=WGS84')

Step 2: Check NA value in tf_line$BanType If NA exist in value, then palette will generate error

unique(tf_line$BanType)
## [1] "NON-APPLICABLE"                               
## [2] "PERMANENT"                                    
## [3] "DECLARED"                                     
## [4] "STATE OWNED - NO WORCESTER WINTER PARKING BAN"
## [5] NA
library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
tf_line <- tf_line %>%
  mutate(BanType = ifelse(is.na(BanType), 'NON-APPLICABLE', BanType))

unique(tf_line$BanType)
## [1] "NON-APPLICABLE"                               
## [2] "PERMANENT"                                    
## [3] "DECLARED"                                     
## [4] "STATE OWNED - NO WORCESTER WINTER PARKING BAN"
palette_line <- colorFactor(palette = mpalette, domain = tf_line$BanType)
# 
leaflet() %>%
   addProviderTiles(
    "CartoDB.Positron",
    group = "OpenTopo"
  ) %>%
  addPolylines( data = tf_line,
    color = ~palette_line(BanType),
    weight = 2,
    label = ~BanType
      ) %>%
addLegend(data = tf_line,
          position = "bottomright",
          pal = palette_line, 
          values = ~BanType,
          title = "BanType",
          opacity = 1)

Merge all layers into one map

You will need to assign a name to each layer using groups, then use the function to define the base groups (which can be selected one at a time) and the overlay groups (which can be selected independently).

final_map = leaflet() %>%
   addProviderTiles(
                    "CartoDB.Positron",
                    group = "OpenTopo"
  ) %>%
   addProviderTiles(
                    "OpenStreetMap.Mapnik",
                    group = "OpenStreetMap"
  ) %>%
  addPolylines(data = tf_line,
              color = ~palette_line(BanType),
              weight = 2,
              label = ~BanType,
              group = "Winter Parking"
      ) %>%
  addLegend(data = tf_line,
          position = "bottomright",
          pal = palette_line, 
          values = ~BanType,
          title = "Ban Type",
          opacity = 1) %>%
   addCircleMarkers(
           data = tf_water,
            fillColor  = ~palette_pts(SURVEYTYPE),  # Color based on the 'category' column
            fillOpacity = 0.8,           # Adjust opacity
            radius = 5,                  # Set marker size
            stroke = TRUE,               # Add stroke to the markers
            weight = 1,                   # Set stroke weight
            color = "#000000",
            label = tf_water$SURVEYTYPE,
           group = "Water Quality"
    ) %>%
    addLegend(data = tf_water,
          position = "bottomleft",
          pal = palette_pts, 
          values = ~SURVEYTYPE,
          title = "Survey Type",
          opacity = 1) %>%
   addPolygons(data = tf_pond,  color = "blue", label = tf_pond$NAME, fillOpacity = 1,group = "Water Pond") %>%
   addLayersControl(baseGroups = c("OpenTopo", "StamenToner"), overlayGroups = c("Water Pond", "Winter Parking","Water Quality"),
                   position = "topright")
library(htmlwidgets)
saveWidget(final_map, ".\\leaflet_map_R.html", selfcontained = TRUE)

Upload html to Github and generate a web page